#include "ET++.ph"
#include "Converter.h"

#include "Class.h"
#include "Data.h"
#include "Bitmap.h"
#include "DevBitmap.h"
#include "String.h"
#include "ColorMapper.h"
#include "Math.h"
#include "TypeMatcher.h"
#include "ET_stdio.h"

#include "ET_tiff.h"

static const Symbol cDocTypeTIFF("TIFF");

//---- TIFFMatcher --------------------------------------------------------------

class TIFFMatcher: public TypeMatcher {
public:
	TIFFMatcher(const Symbol &t) : TypeMatcher(t, 0, 0, FALSE)
		{ }
	bool MatchContents(Data*, const char *b, int len)
		{ return len >= 2 && (*b == b[1]) && (*b == 'M' || *b == 'I'); }
	bool MatchExtension(Data*, const char *e)
		{ return StrCmp(e, "tiff", -1, gStdEncoding->UpperCaseMap()) == 0
					|| StrCmp(e, "tif", -1, gStdEncoding->UpperCaseMap()) == 0; }
	Bitmap *GetBitmap()
		{ return gImageIcon; }
};

static TIFFMatcher tiffmatcher(cDocTypeTIFF);

//---- TiffConverter -----------------------------------------------------------

class TiffConverter : public Converter {
public:
	MetaDef(TiffConverter);
	TiffConverter()
		{ }
	const char *AsString()
		{ return "TIFF"; }
	Object *Convert(Data *data, Class *want);
	bool CanConvert(Data *data, Class *want)
		{ return data->Type() == cDocTypeTIFF && want == Meta(Bitmap); }
	bool CanConvertTo(Object *op)
		{ return op->IsKindOf(Bitmap); }
	bool ConvertTo(Data *data, Object *op);
};

NewMetaImpl0(TiffConverter,Converter);

static int checkcmap(int n, u_short *r, u_short *g, u_short *b)
{
	while (n-- > 0)
		if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256)
			return 16;
	return 8;
}

#define CVT(x,bits) (((x) * 255) / ((1L << (bits))-1))

Object *TiffConverter::Convert(Data *data, Class*)
{
	u_short *redcmap, *greencmap, *bluecmap;
	int i, row, len;
	u_long width, height;
	u_short bitspersample,
			samplesperpixel,
			photometric,
			matteing;
	TIFF *tif;
	Bitmap *b;
	DevBitmap *dbm;
	u_char *buf= 0;
	BitmapInfo bi(8, 8, 1);
	RGB rgb;
	u_long *raster;

	tif= TIFFOpen((char*) data->Name(), "r");
	if (tif == 0)
		return 0;
		
	if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample))
		bitspersample= 1;
	switch (bitspersample) {
	case 1: case 2: case 4:
	case 8: case 16:
		break;
	default:
		TIFFClose(tif);
		return 0;
	}

	len= 1 << bitspersample;

	if (!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel))
		samplesperpixel= 1;
	switch (samplesperpixel) {
	case 1: case 3: case 4:
		break;
	default:
		TIFFClose(tif);
		return 0;
	}

	TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);

	if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)) {
		switch (samplesperpixel) {
		case 1:
			photometric= PHOTOMETRIC_MINISBLACK;
			break;
		case 3: case 4:
			photometric= PHOTOMETRIC_RGB;
			break;
		default:
			TIFFClose(tif);
			return 0;
		}
	}

	if (!TIFFGetField(tif, TIFFTAG_MATTEING, &matteing))
		matteing= 0;
	//fprintf(stderr, "matteing: %d\n", matteing);

	switch (photometric) {

	case PHOTOMETRIC_PALETTE:
		b= new Bitmap(Point((int)width, (int)height), 8);
		dbm= b->GetDevBitmap();
		bi= BitmapInfo(b->Size(), bitspersample, 1);
		
		TIFFGetField(tif, TIFFTAG_COLORMAP, &redcmap, &greencmap, &bluecmap);
		if (checkcmap(len, redcmap, greencmap, bluecmap) == 16) {
			for (i= len-1; i > 0; i--) {
				redcmap[i]= (u_short) CVT(redcmap[i], 16);
				greencmap[i]= (u_short) CVT(greencmap[i], 16);
				bluecmap[i]= (u_short) CVT(bluecmap[i], 16);
			}
		}
		for (i= 0; i < len; i++) {
			rgb.red= redcmap[i];
			rgb.green= greencmap[i];
			rgb.blue= bluecmap[i];
			if (i == 0 && matteing > 0)
				rgb.alpha= 0;
			else
				rgb.alpha= 255;
			b->ChangeRGB(i, &rgb);
		}
		
		buf= new u_char[TIFFScanlineSize(tif)];
		
		for (row= 0; row < height; row++) {
			if (TIFFReadScanline(tif, buf, row, 0) < 0)
				break;
			dbm->SetRow(&bi, row, buf);
		}
		
		break;

	case PHOTOMETRIC_MINISBLACK:
	case PHOTOMETRIC_MINISWHITE:
		b= new Bitmap(Point((int)width, (int)height), 8);
		dbm= b->GetDevBitmap();
		bi= BitmapInfo(b->Size(), bitspersample, 1);
		
		for (i= 0; i < len; i++) {
			if (photometric == PHOTOMETRIC_MINISBLACK)
				rgb.red= rgb.green= rgb.blue= (short) CVT(i,bitspersample);
			else
				rgb.red= rgb.green= rgb.blue= (short) CVT((len-1)-i,bitspersample);
			b->ChangeRGB(i, &rgb);
		}
		
		buf= new u_char[TIFFScanlineSize(tif)];
		
		for (row= 0; row < height; row++) {
			if (TIFFReadScanline(tif, buf, row, 0) < 0)
				break;
			dbm->SetRow(&bi, row, buf);
		}
		break;

	default:
		b= new Bitmap(Point((int)width, (int)height), 24);
		bi= BitmapInfo(b->Size(), 32, 1);
		raster= new u_long[width * height];
		
		if (TIFFReadRGBAImage(tif, width, height, raster, 0) == 1) {
			/*
			b->GetDevBitmap()->SetData(&bi, raster);
			*/
			register u_int x, y;
			register u_long *c, pixel;
			for (y= 0; y < height; y++) {
				c= &raster[(height-y)*width];
				for (x= 0; x < width; x++) {
					pixel= *c++;
					rgb.red= (short) TIFFGetR(pixel);
					rgb.green= (short) TIFFGetG(pixel);
					rgb.blue= (short) TIFFGetB(pixel);
					rgb.alpha= (short) TIFFGetA(pixel);
					b->SetRGB(x, y, &rgb);
				}
			}
		} else {
			SafeDelete(b);
			delete raster;
		}
	}
	SafeDelete(buf);
	TIFFClose(tif);
	return b;
}

bool TiffConverter::ConvertTo(Data *data, Object *op)
{
	Bitmap *b= (Bitmap*) op;
	DevBitmap *dbm= b->GetDevBitmap();
	ColorMapper *cmapper= b->GetColorMapper();
	Point e(b->Size());
	int depth= b->GetDepth();
	TIFF *out;

	if ((out= TIFFOpen((char*) data->Name(), "w")) == 0)
		return FALSE;

	TIFFSetField(out, TIFFTAG_IMAGEWIDTH,   (u_long) e.x);
	TIFFSetField(out, TIFFTAG_IMAGELENGTH,  (u_long) e.y);
	TIFFSetField(out, TIFFTAG_ORIENTATION,  ORIENTATION_TOPLEFT);

	int spp;

	if (depth > 8) {
		spp= 3;
	} else {
		spp= 1;
	}
	if (cmapper->HasMask()) {
		//spp++;
		TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, spp);
		//TIFFSetField(out, TIFFTAG_EXTRASAMPLES, 1);
		TIFFSetField(out, TIFFTAG_MATTEING, 1);
	} else
		TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, spp);

	TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, depth > 8 ? 8 : depth);
	TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);

	if (depth > 8) {
		TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
		TIFFSetField(out, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
	} else if (depth > 1) {
		register int i;
		int mapsize= 1 << depth;
		bool grey= FALSE;
		RGB rgb;
		
		if (cmapper->IsGrey() && !cmapper->HasMask() && (depth == 4 || depth == 8)) {
			grey= TRUE;
			for (i= 0; i <= cmapper->MaxPixel(); i++) {
				cmapper->Pixel2RGB(i, &rgb);
				if (rgb.red != i) {
					grey= FALSE;
					break;
				}
			}
		}
		
		if (grey) {
			fprintf(stderr, "IsGrey\n");
			TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
		} else {
			u_short *red=   new u_short[mapsize],
					*green= new u_short[mapsize],
					*blue=  new u_short[mapsize];
			for (i= 0; i <= cmapper->MaxPixel(); i++) {
				cmapper->Pixel2RGB(i, &rgb);
				red[i]=   (rgb.red*0xFFFF)/255;
				green[i]= (rgb.green*0xFFFF)/255;
				blue[i]=  (rgb.blue*0xFFFF)/255;
			}
		
			TIFFSetField(out, TIFFTAG_COLORMAP, red, green, blue);
			TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE);
		
			delete red;
			delete green;
			delete blue;
		}
		TIFFSetField(out, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
	} else {
		TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
		TIFFSetField(out, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
	}
	BitmapInfo bi(e, depth, 1);

	u_char *buf= new u_char[bi.rowlen];
	TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, Math::Max((8*1024)/bi.rowlen, 1));

	for (int row= 0; row < e.y; row++) {
		dbm->GetRow(&bi, row, buf);
		if (TIFFWriteScanline(out, buf, row, 0) < 0)
			break;
	}
	TIFFClose(out);
	return TRUE;
}

